package edu.hawaii.myisern.model;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static java.util.Arrays.asList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import net.sourceforge.stripes.action.FileBean;

import edu.hawaii.myisern.appconfig.jaxb.AppConfig;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.collaborations.jaxb.Collaborations;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.organizations.jaxb.Organizations;
import edu.hawaii.myisern.researchers.jaxb.Researcher;
import edu.hawaii.myisern.researchers.jaxb.Researchers;

/**
 * Implements a singleton IsernModel instance. The command classes manipulate this singleton in
 * response to user input. To prevent multi-threaded access from corrupting the stack, all
 * operations on the singleton IsernModel instance are synchronized.
 * 
 * @author Kevin English
 * 
 */

public class IsernManager {
  /** The static application configuration variable. **/
  private static AppConfig appConfig;
  /** The name of the application configuration xml file **/
  private static final String APP_CONFIG_XML_FILE = "appconfig.xml";
  /** The default application data directory.*/
  public static String DEFAULT_DATA_DIRECTORY = ".myisern";
  /** The application data directory. Should be created by the user. */
  public static String dataDirectory = DEFAULT_DATA_DIRECTORY;
  /** The path to the xml files * */
  //public static String xmlFilesPath = getXmlFilesPath(dataDirectory);
  /** A static list of valid years for collaborations. **/ 
  private static List<String> validYears;
  /** The singleton instance of our IsernModel. **/  
  private static IsernModel model;
  /** A static list of orginzation types. **/
  private static List<String> organizationTypes = asList("Academic", "Government", "Classified",
      "Classified");
  static {
    validYears = new ArrayList<String>();
    
    for (int i = 1990 ; i <= 2010; i++ ) {
      validYears.add("" + i );
    }    
	  System.out.println("Static calling  loadApplication");
    loadApplication();
  }

  /**
   * Initialize the application setting from the appConfig object. 
   * This includes loading researchers, organizations and collorations
   * from their respective xml files. The appconfig.xml is including with distribution
   * and tells the system which xml files to user. 
   * @param appConfig the AppConfig that contorl what to reload.
   */
  public static void loadApplication(AppConfig appConfig) {
    model = new IsernModel();
    String xmlFilesPath = getXmlFilesPath(dataDirectory);
    boolean exists = (new File(xmlFilesPath)).exists();
    if (exists) {
      System.out.println("Directory Exists");
    }
    else {
      System.out.println("Directory DOES NOT Exists");
      boolean success = (new File(xmlFilesPath)).mkdir();
      if (!success) {
        System.out.println("Create Directory failed ");
      }
    }

    try {
      model.setPath(xmlFilesPath);
      model.setCollaborationsFile(appConfig.getCollaborationsXmlFilename());
      model.setOrganizationsFile(appConfig.getOrganizationsXmlFilename());
      model.setResearchersFile(appConfig.getResearchersXmlFilename());
      model.load();
    }
    catch (JAXBException e1) {
      e1.printStackTrace();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  
  /**
   * Initialize the application setting from the appconfig.xml file. 
   * This includes loading researchers, organizations and collorations
   * from their respective xml files. The appconfig.xml is including with distribution
   * and tells the system which xml files to user. 
   */
  public static void loadApplication() {
    try {
      dataDirectory = DEFAULT_DATA_DIRECTORY;
      String xmlFilesPath = getXmlFilesPath(dataDirectory);
      appConfig = loadAppConfig(xmlFilesPath + File.separatorChar + APP_CONFIG_XML_FILE);
      System.out.println("XML FILES found in " + xmlFilesPath);
      loadApplication(appConfig);
    }
    catch (JAXBException e) {
      e.printStackTrace();
    }
  }
  
  /**
   * Sets the XML files' directory.
   * @param dir the directory name.
   */
  public static void setDataDirectory(String dir) {
    dataDirectory = dir;
  }

  /**
   * Return the path according to the set name of XML files. 
   * @param dataDir the set name of XML files.
   * @return String that represent the path.
   */
  public static String getXmlFilesPath(String dataDir) {
    return System.getProperty("user.home") + File.separatorChar + dataDir;
  }
  /**
   * Load the application configuration from an xml file.
   * 
   * @param file The appconfig xml file to load.
   * @return An application configuration object.
   * @throws JAXBException When an error occurred loading the xml file.
   */
  public static synchronized AppConfig loadAppConfig(String file) throws JAXBException {
    AppConfig a = (AppConfig) JAXBContext.newInstance(AppConfig.class).createUnmarshaller()
        .unmarshal(new File(file));
    return a;
  }
  /**
   * Get the directory for the where application data files are stored.
   * 
   * @return An application configuration object.
   */
  public static String getApplicationDirectory() {
    return getXmlFilesPath(dataDirectory);
  }
  /**
   * Get the application configuration object.
   * 
   * @return An application configuration object.
   */
  public static synchronized AppConfig getAppConfig() {
    return appConfig;
  }
  /**
   * Gets the current list of researchers, wrapper for IsernModel method.
   * 
   * @return List<Researcher> A list of researchers
   */
  public synchronized List<Researcher> getResearchers() {
    return model.getResearcherList();
  }

  /**
   * Get researcher by id.
   * 
   * @param id The Id of the researcher to retrieve.
   * @return a Researcher, null if not found.
   */
  public synchronized Researcher getResearcher(Id id) {
    return model.getResearchers().get(id);
  }
  /**
   * Gets the current list of Collaboration, wrapper for  IsernModel method.
   * 
   * @return A list of Collaborations.
   */
  public synchronized List<Collaboration> getCollaborations() {
    return model.getCollaborationList();
  }
  /**
   * Gets the a collaboration by id.
   * 
   * @param id The Id of the collaboration to retrieve.
   * @return a Collaboration, null if not found.
   */
  public synchronized Collaboration getCollaboration(Id id) {
    return model.getCollaborations().get(id);
  }
  /**
   * Gets the current list of Organizations, wrapper for  IsernModel method.
   * 
   * @return A list of Organizations.
   */
  public synchronized List<Organization> getOrganizations() {
    return model.getOrganizationList();
  }
  /**
   * Gets the a organization by id.
   * 
   * @param id The Id of the researcher to retrieve.
   * @return a Organization, null if not found.
   */
  public synchronized Organization getOrganization(Id id) {
    return model.getOrganizations().get(id);
  }

  /**
   * Save all data to xml file, wrapper for  IsernModel method.
   * 
   * @throws JAXBException If there's a jaxb exception.
   * @throws IOException If there's a IO exception.
   */
  public synchronized void save() throws IOException, JAXBException {
    model.save();
  }

  /**
   * Updates the organization with the given id, overwriting the data with data in the 
   * provided object.
   * 
   * @param id The Id for the given field.
   * @param organization The organization object with data to overwrite old organization.
   */
  public synchronized void updateOrganization(Id id, Organization organization) {
    model.updateOrganization(id, organization);
  }

  /**
   * Creates a new organization.
   * 
   * @param organization The organization object.
   */
  public synchronized void createOrganization(Organization organization) {
    updateOrganization(null, organization);
  }

  /**
   * Updates the researcher with the given id, overwriting the data with data in the 
   * provided object.
   * 
   * @param id The Id for the given field.
   * @param researcher The researcher object.
   */
  public synchronized void updateResearcher(Id id, Researcher researcher) {
    model.updateResearcher(id, researcher);
  }

  /**
   * Create new researcher.
   * 
   * @param researcher The researcher object.
   */
  public synchronized void createResearcher(Researcher researcher) {
    updateResearcher(null, researcher);
  }

  /**
   * Update collaboration with the given id, overwriting the data with data in the 
   * provided object..
   * 
   * @param id The Id for the given field.
   * @param collaboration The collaboration object.
   */
  public synchronized void updateCollaboration(Id id, Collaboration collaboration) {
    model.updateCollaboration(id, collaboration);
  }

  /**
   * Create new collaboration.
   * 
   * @param collaboration The researcher object.
   */
  public synchronized void createCollaboration(Collaboration collaboration) {
    updateCollaboration(null, collaboration);
  }

  /**
   * Remove a collaboration, organization or researcher, according to the id
   * 
   * @param id the given Id.
   * @throws IOException when matching object is not found
   */
  public synchronized void remove(Id id) throws IOException {
    model.remove(id);
  }
  /**
   * Save the researchers to xml file.
   * 
   * @throws JAXBException If there's a jaxb exception.
   * @throws IOException If there's a IO exception.
   */
  public synchronized void saveResearchers() throws IOException, JAXBException {
    model.saveResearchers();
  }

  /**
   * Saves the Organizations to xml file.
   * 
   * @throws JAXBException If there's a jaxb exception.
   * @throws IOException If there's a IO exception.
   */
  public synchronized void saveOrganizations() throws IOException, JAXBException {
    model.saveOrganizations();
  }

  /**
   * Save the Collaborations to xml file.
   * 
   * @throws JAXBException If there's a jaxb exception.
   * @throws IOException If there's a IO exception.
   */
  public synchronized void saveCollaborations() throws IOException, JAXBException {
    model.saveCollaborations();
  }

  /**
   * Get the types of organizations.
   * 
   * @return A list of organization types.
   */
  public synchronized List<String> getOrganizationTypes() {
    return organizationTypes;
  }
  /**
   * Return a Set of all IDs in use.
   * 
   * @return a Set of IDs
   */
  public synchronized Set<Id> getIds() {
    return model.getIds();
  }
  /**
   * Get the year options.
   * 
   * @return A List of years (strings)
   */
  public synchronized List<String> getYearOptions() {
    return validYears;
  }  
  
  /**
   * Get collaborations by organization
   * 
   * @param orgId the organization to look for
   * @return a list of collaborations involving the organization
   */
  public synchronized List<Collaboration> getCollaborationsByOrganization(Id orgId) {
    return new ArrayList<Collaboration>(model.getCollaborationsByOrganization(orgId).values());
  }
  
  /**
   * Get collaborations by researcher
   * 
   * @param researcherId the ID of the researcher to look for
   * @return a list of collaborations involving the organization to which the researcher belongs.
   */
  public synchronized List<Collaboration> getCollaborationsByResearcher(Id researcherId) {
    return new ArrayList<Collaboration>(model.getCollaborationsByResearcher(researcherId).values());
  }
  
  /**
   * Join Strings in a list into a single String.
   * Each value is divided by the given delimiting String.
   * 
   * @param list - the incoming List of String.
   * @param delimit - the given delimiting String.
   * @return - A String.
   */
  public static String joinListToString(List<String> list, String delimit) {
    String output = "";
    for (String value : list) {
      output += value + delimit;
    }
    output.split(output);
    return output;
  }

  /**
   * Split the String by the delimiting String and put the results in a List of String
   * 
   * @param input - the target String.
   * @param delimit - the delimiting String.
   * @return - A List of String.
   */
  public static List<String> splitStringToList(String input, String delimit) {
    List<String> list = new ArrayList<String>();
    if (input != null) {
      for (String value : input.split(delimit)) {
        list.add(value.trim());
      }
    }
    return list;
  }

  /**
   * Import data from imported Xml file.
   * 
   * @param importXmlFile the import XML file. 
   * @return 0 - an organization XML file is imported.
   *         1 - a collaboration XML file is imported.
   *         2 - a researcher XML file is imported.
   *         3 - error when imported. 
   */
  public int importXml(FileBean importXmlFile) {
    try {
      for (Collaboration c : ((Collaborations) JAXBContext.newInstance(Collaborations.class)
          .createUnmarshaller().unmarshal(importXmlFile.getInputStream())).getCollaboration()) {
        try {
          model.addCollaboration(c);
        }
        catch (IOException e) {
          System.out.println(e.getMessage() + ", skip.");
        }
      }
      return 1;
    }
    catch (JAXBException e) {
      System.out.println("Not a Collaboration XML file.");
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    
    try {
      for (Organization o : ((Organizations) JAXBContext.newInstance(Organizations.class)
          .createUnmarshaller().unmarshal(importXmlFile.getInputStream())).getOrganization()) {
        try {
          model.addOrganization(o);
        }
        catch (IOException e) {
          System.out.println(e.getMessage() + ", skip.");
        }
      }
      return 0;
    }
    catch (JAXBException e) {
      System.out.println("Not a Organization XML file neither.");
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    

    try {
      for (Researcher r : ((Researchers) JAXBContext.newInstance(Researchers.class)
          .createUnmarshaller().unmarshal(importXmlFile.getInputStream())).getResearcher()) {
        try {
          model.addResearcher(r);
        }
        catch (IOException e) {
          System.out.println(e.getMessage() + ", skip.");
        }
      }
      return 2;
    }
    catch (JAXBException e) {
      System.out.println("Not a Researcher XML file too.");
    }
    catch (IOException e) {
      e.printStackTrace();
    }
    return 3;
  }
}
